<!doctype html>
<html lang="zh-cn">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
  <link rel="shortcut icon" href="/favicon.ico" />
  <script src="/app/ai/js/jquery.min.js"></script>
  <link href="/app/ai/css/bootstrap.min.css?v=5.3" rel="stylesheet" crossorigin="anonymous">
  <script src="/app/ai/js/bootstrap.bundle.min.js?v=5.3" crossorigin="anonymous"></script>
  <!-- markdown css -->
  <link rel="stylesheet" href="/app/ai/css/github-markdown.css?v=<?=ai_css_version()?>">
  <!-- CSS -->
  <link rel="stylesheet" href="/app/ai/css/app.css?v=<?=ai_css_version()?>">
  <title><?=$site_title?></title>
  <meta name="description" content="<?=$site_desc?>" />
</head>

<body class="ready sticky overflow-hidden h-100" data-bs-theme="<?=$theme?>" style="background: var(--ai-body-bg);">

  <!-- webman ai 主应用 -->
  <div id="app" class="h-100 d-flex align-items-center justify-content-center">
    <div class="loading" v-if="showLoading">
      <div class="dot dot1"></div>
      <div class="dot dot2"></div>
      <div class="dot dot3"></div>
    </div>
    <div class="flex-column-reverse flex-md-row container-fluid h-100 d-flex shadow-sm ai-container" :class="{'small-window':isSmallWindow}" v-cloak @click="hideAll" @mouseup="onMouseUp()">
      <!-- 左侧工具栏 -->
      <div class="left-bar black-bg" :class="{'rounded-start':!isMobile}" v-show="showAddressBook">
        <a href="/app/user" target="_blank" v-show="!isMobile"><img :src="loginUser.avatar" class="d-none d-md-flex avatar shadow-sm flex-shrink-0"></a>
        <div class="mt-md-4 ai-menu" @click="switchModule('chat')" title="聊天">
          <span v-if="module==='chat'" class="text-primary"><i class="icon-chat-fill"></i></span>
          <span v-else><i class="icon-chat"></i></span>
        </div>

        <template v-for="(menu, key) in setting.menus">
          <div :title="menu.title"  @click="switchModule(key)" class="mt-md-4 ai-menu" v-show="menu.enabled&&(!isMobile||menu.mobile)">
              <span v-if="module===key" v-html="menu.icon.active" class="text-primary d-flex align-center"></span>
              <span class="d-flex align-center" v-else v-html="menu.icon[theme]"></span>
          </div>
        </template>

        <div class="d-none d-md-flex align-items-center justify-content-center flex-column pb-3" >
          <div class="mt-md-4 icon-btn" @click="toggleTheme()" :title="theme==='dark'?'深色主题':'浅色主题'">
            {{theme==="light"?"&#xe7eb;":"&#xe602;"}}
          </div>
          <div class="mt-md-4 icon-btn" @click="saveData('smallWindow' , smallWindow=!smallWindow)" :title="smallWindow?'全屏模式':'窗口模式'">
            {{smallWindow?"&#xe632;":"&#xe637;"}}
          </div>
          <div class="mt-md-4 icon-btn" :class="{selected:module==='setting'}" @click.stop="box.showMore=!box.showMore" title="更多设置">
            {{module==="setting"?"&#xe9f1;":"&#xe9f2;"}}
          </div>
        </div>
      </div>

      <!-- 对话列表 -->
      <div class="chat-bar dark-bg" v-show="showAddressBook&&module==='chat'">
        <div class="d-flex justify-content-center align-items-center px-3 header search-box">
          <div class="d-flex align-items-center w-100">
            <input class="form-control form-control-sm" type="text" v-model="keyword" placeholder="搜索">
            <button class="btn btn-sm border ms-2 me-0 f15 font-weight-bold add-btn" @click.stop="showRoleInfoBox"><span class="iconfont p-0">&#xe6a6;</span></button>
          </div>
        </div>
        <div class="chat-list">
          <template v-for="item in filter">
            <div class="item" @click="switchRoleId(item.roleId)" :class="{'selected-bg': roleId==item.roleId || contextMenu.roleId==item.roleId}" @contextmenu.prevent="openContextMenu(item.roleId, $event)">
              <div class="d-flex">
                <img class="avatar" :src="item.avatar+'?v=5.1'" alt="avatar"/>
                <div class="ms-2">
                  <div class="name">{{item.name}}</div>
                  <div class="text-secondary-sm text-truncate">{{item.desc}}</div>
                </div>
              </div>
              <div class="text-right text-secondary f12">
                <div style="height:1.3rem">{{formatDate(item.lastTime)}}</div>
                <div class="iconfont" v-if="item.pinned">&#xe677;</div>
              </div>
            </div>
          </template>
        </div>
      </div>

      <!-- 聊天框 -->
      <div class="chat-box gray-bg rounded-end" :style="{width:showAddressBook?'calc(100% - 250px)':'100%'}" :class="{'slide-in': isSlidedIn, 'slide-out': isSlidedOut}" v-show="(!isMobile || !showAddressBook) && module==='chat'">
        <div class="header d-flex justify-content-between shadow-sm" @click="scrollToTop()">
          <b class="iconfont" @click="slideOut" v-show="isMobile">&#xe9ef;</b>
          <span v-html="chat.name"></span>
          <b class="iconfont" @click.stop="editRole(roleId)">&#xe9f8;</b>
        </div>
        <div class="body" :style="{height: 'calc(100% - '+(footerHeight+(isMobile?50:60))+'px)'}">
          <ul class="list-unstyled overflow-auto h-100 mb-0 message-list" ref="messageBox">
            <template v-for="message in chat.messages" :key="message.id">
              <li class="d-flex mt-4" :class="{'flex-row-reverse':message.role=='user'}">
                <img class="avatar message-avatar flex-shrink-0" alt="avatar" :src="message.role!=='user'?chat.avatar+'?v=5.1':loginUser.avatar" width="40" height="40">
                <div class="position-relative d-flex align-items-center" @mouseenter="hoverMessageId=message.id" @mouseleave="hoverMessageId=0">
                  <div class="mx-2 markdown-body message rounded message-bg"
                       v-html="markdown(Array.isArray(message.content)?this.implodeContent(message.content):message.content)||(!message.completed?'<b class=\'animate-blink\'>|</b>':'')"
                  >
                  </div>

                  <div class="p-1 rounded shadow-sm position-absolute message-bg" style="top:-20px;" v-show="hoverMessageId===message.id && message.completed" :style="message.role==='user'?'left:0':'right:0'">
                    <span class="icon-btn p-1" v-show="message.role!=='user'&&message.prompt&&message===this.lastMessage(chat)" @click="regenerate(chat, message)">&#xe60a;</span>
                    <span class="icon-btn block-copy" @click="copyToClipboard(message.content)"></span>
                    <span class="icon-btn p-1" v-if="supportSpeak" @click="speak(message.content)">&#xea43;</span>
                    <span class="icon-btn p-1" @click="deleteMessage(message.id)">&#xe680;</span>
                  </div>
                </div>
              </li>
            </template>
          </ul>
          <div class="stop-btn">
            <button type="button" class="btn btn-sm btn-outline-secondary" v-show="chat.loading" @click="cancel()" style="display: none">停止</button>
          </div>

          <div class="images-box rounded" v-if="chat.images && chat.images.length" >
            <div  class="images-item" v-for="(image,index) in chat.images">
              <img class="rounded" :src="image" @click="previewImage(image)">
              <span class="del" @click="deleteImage(index)"><i class="bi bi-x-lg"></i></span>
            </div>
          </div>

        </div>
        <div class="footer position-relative" v-show="chat.name" :style="{height: footerHeight + 'px'}">
          <div style="height:3px;cursor:s-resize" @mousedown="dragEagle" v-if="!isMobile"></div>
          <div style="border-top:2px solid var(--bs-primary)" :class="{'fade-out':uploadPercent>=100}" :style="'width:' + uploadPercent+'%'"></div>
          <div class="tools" @click="scrollToBottom(true)">
            <span class="icon chat-tools-item" v-if="!isMobile"  title="通讯录关闭/开启" @click.stop="showAddressBook=!showAddressBook" :class="{'text-primary':!showAddressBook}">&#xe84a;</span>
            <span class="icon chat-tools-item" title="历史话题" @click.stop="showHistory(chat.roleId)">&#xe9ee;</span>
            <span class="icon chat-tools-item" title="新话题" @click.stop="newChat">&#xea31;</span>
            <span class="icon chat-tools-item" title="参数调整" :class="{selected:box.showParams}" @click.stop="showPanel('Params')">&#xea0a;</span>
            <span class="icon chat-tools-item" title="语音对话" v-if="setting.audio.enable_xunfei_iat" @click.stop="toggleVoice()" :class="{'text-primary':chat.voice}">&#xe743;</span>
            <span class="icon chat-tools-item" title="上传图片" v-show="chat.model&&modelSupportImage(chat.model)" @click.stop="openUploadImage">&#xea04;</span>

            <form style="display: none" ref="uploadForm"><input type="file" ref="uploadInput" @change="uploadImage"></form>

            <span class="icon chat-tools-item float-end f20 d-none d-md-block" title="发送方式" :class="{selected:box.showSendMethod}" @click.stop="showPanel('SendMethod')">&#xe696;</span>
          </div>
          <div v-show="chat.voice" class="user-select-none">

            <div class="d-flex justify-content-center align-items-center text-secondary f13" :style="{height:(footerHeight-65)+'px'}">
              <template v-if="microphoneError">
                <span class="text-danger">{{microphoneError}}</span>
              </template>
              <template v-else>
                <div class="d-flex flex-column align-items-center" v-if="!isMobile">
                  <div class="rounded-pill iconfont voice-btn bg-primary text-light f28 d-flex align-items-center justify-content-center mb-2"
                       :class="{'btn-primary-light':chat.voice==='listening', 'bg-secondary':chat.loading}"
                       @mousedown.stop="onMouseDown()" @mouseup.stop="onMouseUp()" @mouseleave="mouseLeave=true" @mouseenter="mouseLeave=false" @click.prevent>{{!chat.loading?"&#xea44;":"&#xea45;"}}</div>
                  <div :class="{'text-danger':mouseDown&&mouseLeave}">{{voiceBtnTip()}}</div>
                </div>
                <div class="d-flex flex-column align-items-center" v-else>
                  <div class="btn" @contextmenu.prevent.stop
                       :class="{'btn-primary-light':chat.voice==='listening'&&!mouseLeave, 'btn-secondary':chat.loading, 'btn-danger':mouseDown&&mouseLeave, 'btn-primary':!mouseDown||!mouseLeave}"
                       @touchstart="onTouch($event)" @touchend="onTouchEnd()" @touchmove="onTouchMove($event)" @click.prevent>
                    {{voiceBtnTip()}}
                  </div>
                </div>
              </template>
            </div>
          </div>

          <textarea :style="{height: (footerHeight - (isMobile?70:90))+'px'}" v-show="!chat.voice" @drop="handleDrop" @paste="handlePaste" class="input"  placeholder="说点什么吧..." v-model="chat.content" @keyup.enter="handleEnter" ref="input" @focus="handleInputFocus" @input="resizeInput()" @change="resizeInput()"></textarea>

          <div class="footer-txt-box" v-if="!isMobile">
            <a class="me-2" v-if="setting.icp" target="_blank" href="http://beian.miit.gov.cn/">{{setting.icp}}</a>
            <a class="me-2" v-if="setting.beian" target="_blank" :href="'http://www.beian.gov.cn/portal/registerSystemInfo?recordcode='+(setting.beian.match(/\d+/)||[])[0]">{{setting.beian}}</a>
            <span class="me-2" v-if="setting.footer_txt" v-html="setting.footer_txt"></span>
          </div>
          <div class="send-btn"><button v-show="!chat.voice" class="btn btn-sm btn-primary px-3 ms-3 iconfont f18" :disabled="chat.loading||!chat.content" @click="send()">&#xea1d;</button></div>
          <button v-show="!isMobile&&chat.voice==='listening'" class="btn btn-sm btn-primary px-3 ms-3 send-btn" :disabled="chat.voice!=='listening'" @click="cancelVoice()">取消</button>
        </div>
      </div>

      <!-- 聊天框里相关浮层 -->
      <div class="overlay bg-transparent" v-show="showShadowLayer&&module==='chat'" @contextmenu.prevent="hideAll">
        <!-- 对话列表右键菜单 -->
        <div class="position-fixed shadow-sm p-2 rounded cursor-pointer black-bg" @click.stop :style="{top:contextMenu.top+'px', left:contextMenu.left+'px'}" v-show="box.showContextMenu">
            <a class="dropdown-item iconfont" @click="pinRole(contextMenu.roleId)">&#xe677; 置顶</a>
            <a class="dropdown-item iconfont" @click="editRole(contextMenu.roleId)">&#xe681; 编辑</a>
            <a class="dropdown-item iconfont" @click="deleteRole(contextMenu.roleId)">&#xe680; 删除</a>
        </div>

        <!-- 参数设置 -->
        <div class="params-box border shadow-sm slide-up" @click.stop v-show="box.showParams" :style="{left:showAddressBook?'300px':'0', bottom:footerHeight+'px'}">
          <div class="font-weight-bolder mt-1 mb-3">参数调整</div>
          <form>
            <div class="form-group row align-items-center">
              <label for="Model" class="col-sm-3 col-form-label">模型</label>
              <div class="col-sm-7">
                <select class="form-select form-select-sm" id="Model" v-model="chat.model">
                  <option v-for="(name, value) in setting.defaultModels" :value="value">{{name}}</option>
                </select>
              </div>
            </div>
            <div v-if="chat.model&&!(/(cogview|dall)/.test(chat.model))">
              <div class="form-group row align-items-center">
                <label for="ContextNum" class="col-sm-3 col-form-label">上下文数 <span class="text-secondary" v-if="isMobile">{{chat.contextNum}}</span></label>
                <div class="col-sm-7">
                  <input type="range" class="form-range" min="0" max="32" v-model="chat.contextNum">
                </div>
                <div class="col-sm-2" v-if="!isMobile">
                  <input type="text" class="form-control form-control-sm" id="ContextNum" v-model="chat.contextNum">
                </div>
              </div>
              <div class="form-group row align-items-center">
                <label for="MaxTokens" class="col-sm-3 col-form-label">最大token <span class="text-secondary" v-if="isMobile">{{chat.maxTokens}}</span></label>
                <div class="col-sm-7">
                  <input type="range" class="form-range" min="1" max="4096" v-model="chat.maxTokens">
                </div>
                <div class="col-sm-2" v-if="!isMobile">
                  <input type="text" class="form-control form-control-sm" id="MaxTokens" v-model="chat.maxTokens">
                </div>
              </div>
              <div class="form-group row align-items-center">
                <label for="Temperature" class="col-sm-3 col-form-label">创造性 <span class="text-secondary" v-if="isMobile">{{chat.temperature}}</span></label>
                <div class="col-sm-7">
                  <input type="range" class="form-range" min="0" max="1" step="0.1"  v-model="chat.temperature">
                </div>
                <div class="col-sm-2" v-if="!isMobile">
                  <input type="text" class="form-control form-control-sm" id="Temperature" v-model="chat.temperature">
                </div>
              </div>
              <div class="form-group row align-items-center">
                <label for="Language" class="col-sm-3 col-form-label">语言</label>
                <div class="col-sm-7">
                  <select class="form-select form-select-sm" id="Language" v-model="chat.language">
                    <option value="zh_cn">zh_cn</option>
                    <option value="en_us">en_us</option>
                  </select>
                </div>
              </div>
              <div class="form-group row align-items-center" v-if="setting.audio.enable_yingying_tts || setting.audio.enable_gpt_tts">
                <label for="Speaker" class="col-sm-3 col-form-label">朗读者</label>
                <div class="col-sm-7">
                  <select class="form-select form-select-sm" id="Speaker" v-model="chat.speaker">
                    <option value="yingying" v-if="setting.audio.enable_yingying_tts">莹莹</option>
                    <option value="alloy" v-if="setting.audio.enable_gpt_tts">Alloy</option>
                    <option value="echo" v-if="setting.audio.enable_gpt_tts">Echo</option>
                    <option value="fable" v-if="setting.audio.enable_gpt_tts">Fable</option>
                    <option value="onyx" v-if="setting.audio.enable_gpt_tts">Onyx</option>
                    <option value="nova" v-if="setting.audio.enable_gpt_tts">Nova</option>
                    <option value="shimmer" v-if="setting.audio.enable_gpt_tts">Shimmer</option>
                  </select>
                </div>
              </div>
            </div>
          </form>
        </div>

        <!-- 发送方式 -->
        <div class="send-method-box shadow-sm" @click.stop v-show="box.showSendMethod" :style="{bottom:footerHeight+'px'}">
          <a class="dropdown-item iconfont" @click="sendMethodSelect('Enter')" :class="{'alert-success':sendMethod=='Enter', selected:sendMethod=='Enter', 'ps-4': sendMethod!='Enter'}">Enter发送</a>
          <a class="dropdown-item iconfont" @click="sendMethodSelect('Ctrl-Enter')" :class="{'alert-success':sendMethod=='Ctrl-Enter', selected:sendMethod=='Ctrl-Enter', 'ps-4': sendMethod!='Ctrl-Enter'}">Ctrl+Enter发送</a>
        </div>

        <!-- 历史对话 -->
        <div class="right-box shadow-sm h-100 p-4 chat-list black-bg" v-show="box.showHistory" @click.stop :class="{'slide-in' : isMobile||!smallWindow}">
          <input class="form-control form-control-sm" type="text" v-model="historyKeyword" placeholder="搜索">

          <div v-if="!Object.keys(historyItems).length" class="d-flex flex-column align-items-center justify-content-center">
            <div class="iconfont text-secondary mt-5 cursor-default" style="font-size:60px;">&#xe9e4;</div>
            <div class="text-secondary mt-2">没有相关记录</div>
          </div>
          <div v-else class="mt-3 py-1 px-1 px-md-3 item rounded align-items-center cursor-pointer" v-for="item in historyItems" @click="historyGet(chat.roleId, item.chatId)">
            <div>
              <div class="text-truncate"><b>{{item.title}}</b></div>
              <div class="text-secondary f12 mt-1">{{formatDate(item.time)}}</div>
            </div>
            <div class="icon-btn p-1 f18" @click.stop="deleteHistory(chat.roleId, item.chatId)">&#xe680;</div>
          </div>

        </div>

        <!-- 角色设置 -->
        <div class="center-box border shadow-sm slide-up" @click.stop v-show="box.showRoleInfo">
          <h4 class="mb-4">角色设置</h4>
          <form enctype="multipart/form-data" id="avatarForm" class="mb-3">
            <img :src="roleInfo.avatar" height="64" width="64" class="rounded-pill">
            <label class="btn btn-primary btn-sm uploadBtn font-size-14 ms-4">
              上传头像
              <input type="file" name="avatar" id="avatar" class="d-none" accept="image/*" @change="uploadAvatar">
            </label>
          </form>
          <div class="form-group row no-gutters align-items-center">
            <label for="name" class="col-sm-3 col-form-label">昵称</label>
            <div class="col-sm-9">
              <input type="text" class="form-control form-control-sm" id="name" v-model="roleInfo.name">
            </div>
          </div>
          <div class="form-group row no-gutters align-items-center">
            <label for="desc" class="col-sm-3 col-form-label">简介</label>
            <div class="col-sm-9">
              <input type="text" class="form-control form-control-sm" id="desc" v-model="roleInfo.desc">
            </div>
          </div>
          <div class="form-group row no-gutters align-items-center">
            <label for="greeting" class="col-sm-3 col-form-label">问候语</label>
            <div class="col-sm-9">
              <input type="text" class="form-control form-control-sm" id="greeting" v-model="roleInfo.greeting">
            </div>
          </div>
          <div class="form-group row no-gutters align-items-center">
            <label for="rolePrompt" class="col-sm-3 col-form-label">角色指令</label>
            <div class="col-sm-9">
              <textarea class="form-control form-control-sm" rows="3" id="rolePrompt" v-model="roleInfo.rolePrompt"></textarea>
            </div>
          </div>
          <div class="d-flex justify-content-center mt-4">
            <div>
              <button type="submit" class="btn btn-secondary" @click="hideAll">取消</button>
              <button type="submit" class="btn btn-primary ms-2" @click="saveRole()">保存</button>
            </div>
          </div>
        </div>

      </div>

      <!-- 菜单的iframe -->
      <template v-for="(menu, key) in setting.menus">
        <template v-if="menu.keep">
        <div class="page-box w-100 h-100" v-show="module===key">
          <iframe v-if="menu.urlKeep" class="iframe" :id="key" :src="menu.urlKeep" @load="handleIframeLoaded(key)" style="display:none"></iframe>
        </div>
        </template>
        <template v-else>
          <div class="page-box w-100 h-100" v-if="module===key">
            <iframe class="iframe" :id="key" :src="menu.url" @load="setIframeTheme(key)" style="display:none"></iframe>
          </div>
        </template>
      </template>

      <!-- 关于 -->
      <div class="border shadow-sm card p-0 overflow-hidden center-div" style="max-width:360px;z-index: 10001" v-show="box.showAiInfo" @click.stop>
        <img src="/app/ai/upload/img/20240416/16661e848c8371.png">
        <div class="card-body">
          <h5 class="card-title"><?=$site_title?></h5>
          <p class="card-text"><?=$site_desc?></p>
          <p class="card-text text-secondary">版本 <?=config('plugin.ai.app.version')?></p>
          <a v-show="showBuyLink" href="https://vb6.pro" class="btn btn-primary btn-sm" target="_blank">了解更多</a>
        </div>
      </div>

      <!-- 系统菜单 -->
      <div class="more-box shadow-sm border-top rounded" style="z-index:10000" @click.stop v-show="box.showMore" :class="{'slide-up':box.showMore}">
        <a class="dropdown-item py-2 px-4 cursor-pointer" @click="showPanel('AiInfo')">关于AI</a>
        <a class="dropdown-item py-2 px-4 cursor-pointer" @click="resetSystem">重置系统</a>
        <a class="dropdown-item py-2 px-4 cursor-pointer" @click="switchModule('me')" v-show="!loginUser.nickname">登录</a>
        <a class="dropdown-item py-2 px-4 cursor-pointer" @click="logout" v-show="loginUser.nickname">退出</a>
      </div>

      <!-- 图片预览 -->
      <div class="overlay img-preview" ref="image-preview-box" style="display:none;position:fixed">
        <span class="close">&times;</span>
        <img alt="展示图片" ref="image-preview">
      </div>

    </div>
  </div>


  <audio id="chat-message-audio">
    <source id="chat-message-audio-source" src="" type="audio/mpeg" />
  </audio>

  <!-- 在线统计代码 -->
  <script src="/app/ai/js/push.js"></script>

  <!-- 代码高亮 -->
  <script src="/app/ai/js/highlight.min.js"></script>

  <!-- markdown -->
  <script src="/app/ai/js/markdown-it.min.js"></script>

  <!-- 数学公式 -->
  <link  rel="stylesheet" href="/app/ai/css/katex.min.css">
  <script src="/app/ai/js/katex.min.js"></script>
  <script src="/app/ai/js/texmath.js"></script>

  <!-- webman ai 主应用-->
  <script type="text/javascript" src="/app/ai/js/vue.global.js"></script>
  <script type="module" src="/app/ai/js/app.js?v=<?=ai_js_version()?>"></script>

  <!-- 五彩纸屑效果 -->
  <script src="/app/ai/js/confetti.browser.min.js"></script>

  <!-- js调试 -->
  <?php if (request()->get('debug')) { ?>
  <script src="/app/ai/js/eruda.js"></script>
  <script>eruda.init();</script>
  <?php } ?>


  <script src="/app/ai/js/xunfei/lib/index.umd.js"></script>

  <!-- 图标库 -->
  <link rel="stylesheet" href="/app/ai/css/bootstrap-icons.min.css">

  <script src="/app/user/js/webman.js" async></script>

 <script>
    $(document).click(function () {
      ai.hideAll();
    });

    $(function () {
      function __() {
        const b = document.getElementById("chat-message-audio");
        const p = b.play();
        p && p.then(function(){}).catch(function(e){});
        $(document).unbind("click", __);
      }
      $(document).on("click", __);
    });


  </script>


</body>
</html>
